package com.atguigu.tingshu.album.service.impl;

import com.atguigu.tingshu.album.mapper.AlbumInfoMapper;
import com.atguigu.tingshu.album.mapper.TrackInfoMapper;
import com.atguigu.tingshu.album.mapper.TrackStatMapper;
import com.atguigu.tingshu.album.service.TrackInfoService;
import com.atguigu.tingshu.album.service.VodService;
import com.atguigu.tingshu.common.constant.SystemConstant;
import com.atguigu.tingshu.common.execption.GuiguException;
import com.atguigu.tingshu.common.result.Result;
import com.atguigu.tingshu.common.result.ResultCodeEnum;
import com.atguigu.tingshu.common.util.AuthContextHolder;
import com.atguigu.tingshu.model.album.AlbumInfo;
import com.atguigu.tingshu.model.album.TrackInfo;
import com.atguigu.tingshu.model.album.TrackStat;
import com.atguigu.tingshu.model.user.UserInfo;
import com.atguigu.tingshu.query.album.TrackInfoQuery;
import com.atguigu.tingshu.user.client.UserInfoFeignClient;
import com.atguigu.tingshu.vo.album.*;
import com.atguigu.tingshu.vo.user.UserInfoVo;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.aop.framework.AopContext;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;

import java.math.BigDecimal;
import java.util.*;
import java.util.stream.Collectors;

import static com.atguigu.tingshu.common.result.Result.ok;

@Slf4j
@Service
@SuppressWarnings({"unchecked", "rawtypes"})
public class TrackInfoServiceImpl extends ServiceImpl<TrackInfoMapper, TrackInfo> implements TrackInfoService {


    @Autowired
    private UserInfoFeignClient userInfoFeignClient;

    @Autowired
    private TrackInfoMapper trackInfoMapper;


    @Autowired
    private VodService vodService;

    @Autowired
    private TrackStatMapper trackStatMapper;

    @Autowired
    private AlbumInfoMapper albumInfoMapper;

    @Override
    public Map<String, Object> uploadTrack(MultipartFile file) {
        return vodService.getVodMediaMap(file);
    }

    /**
     * 保存声音信息的方法
     * 该方法包括声音的基本信息保存，统计信息的初始化，以及更新对应专辑的声音计数
     *
     * @param trackInfoVo 声音信息的封装对象，包含需要保存的声音的基本属性
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Long saveTrackInfo(TrackInfoVo trackInfoVo) {

        // 获取当前代理对象，用于调用同一类中的其他方法
        TrackInfoServiceImpl proxy = (TrackInfoServiceImpl) AopContext.currentProxy();

        // 获取当前用户的ID
        Long userId = AuthContextHolder.getUserId();

        // 1.保存声音的基本信息
        TrackInfo trackInfo = new TrackInfo();

        // 1.1 声音基本属性通过拷贝赋值
        BeanUtils.copyProperties(trackInfoVo, trackInfo);
        // 1.2 声音的其它属性手动赋值
        trackInfo.setStatus(SystemConstant.TRACK_STATUS_PASS);
        trackInfo.setUserId(userId);
        // 查询当前专辑中序列号最大的声音，以便为新声音生成序列号
        LambdaQueryWrapper<TrackInfo> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(TrackInfo::getAlbumId, trackInfoVo.getAlbumId());
        wrapper.orderByDesc(TrackInfo::getOrderNum);
        wrapper.last("limit 1");
        TrackInfo maxOrderNumTrackInfo = trackInfoMapper.selectOne(wrapper);
        Integer orderNum = maxOrderNumTrackInfo == null ? 1 : maxOrderNumTrackInfo.getOrderNum() + 1;
        trackInfo.setOrderNum(orderNum);  // 序列号

        // 根据媒体文件ID获取媒体信息，并设置声音信息的相关属性
        TrackMediaInfoVo trackMediaInfoVo = vodService.getMediaInfo(trackInfoVo.getMediaFileId());
        if (trackMediaInfoVo != null) {
            trackInfo.setMediaDuration(new BigDecimal(trackMediaInfoVo.getDuration()));// 声音时长
            trackInfo.setMediaFileId(trackInfoVo.getMediaFileId()); //声音在vod中的文件id
            trackInfo.setMediaUrl(trackMediaInfoVo.getMediaUrl());  // 声音在vod中的地址
            trackInfo.setMediaSize(trackMediaInfoVo.getSize()); // 声音的大小
            trackInfo.setMediaType(trackMediaInfoVo.getType()); // 声音的类型
        }
        // 插入声音信息到数据库
        trackInfoMapper.insert(trackInfo);

        // 2.保存声音的统计信息
        proxy.initTrackStat(trackInfo.getId());

        // 3.修改这个声音对应专辑的包含声音级数
        Long albumId = trackInfo.getAlbumId();
        AlbumInfo albumInfo = albumInfoMapper.selectById(albumId);
        if (albumInfo != null) {
            albumInfo.setIncludeTrackCount(albumInfo.getIncludeTrackCount() + 1);
        }
        // 更新专辑信息
        int i = albumInfoMapper.updateById(albumInfo);
        if (i > 0) {
            // 更新成功
            return albumId;
        }
        return 0L;
    }


    @Override
    public IPage<TrackListVo> findUserTrackPage(
            IPage<TrackListVo> trackInfoVoPage, TrackInfoQuery trackInfoQuery) {
        return trackInfoMapper.findUserTrackPage(trackInfoVoPage, trackInfoQuery);
    }

    @Override
    public TrackInfo getTrackInfo(Long trackId) {
        return trackInfoMapper.selectById(trackId);
    }

    /**
     * 更新声音信息
     *
     * @param trackInfoVo 声音信息的更新数据封装
     * @param trackId 需要更新的声音的ID
     * @throws GuiguException 如果声音不存在，则抛出异常
     */
    @Override
    public void updateTrackInfo(TrackInfoVo trackInfoVo, Long trackId) {

        // 根据声音ID查询数据库中的声音信息
        TrackInfo trackInfo = trackInfoMapper.selectById(trackId);

        // 检查声音信息是否存在，如果不存在则抛出异常
        if (null == trackInfo) {
            throw new GuiguException(201, "声音不存在");
        }

        // 获取旧的声音媒体文件ID和新的声音媒体文件ID
        String oldMediaFileId = trackInfo.getMediaFileId();
        String newMediaFileId = trackInfoVo.getMediaFileId();

        // 将更新数据封装对象中的属性复制到数据库对象中
        BeanUtils.copyProperties(trackInfoVo, trackInfo);

        // 检查新的声音媒体文件ID是否为空且与旧的ID不同，如果满足条件，则更新媒体信息
        if (!StringUtils.isEmpty(newMediaFileId) && !oldMediaFileId.equals(newMediaFileId)) {
            // 获取新声音媒体信息 存放数据库
            TrackMediaInfoVo mediaInfo = vodService.getMediaInfo(newMediaFileId);
            if (mediaInfo != null) {
                // 更新声音信息的媒体属性
                trackInfo.setMediaDuration(new BigDecimal(mediaInfo.getDuration()));
                trackInfo.setMediaType(mediaInfo.getType());
                trackInfo.setMediaFileId(newMediaFileId);
                trackInfo.setMediaUrl(mediaInfo.getMediaUrl());
                trackInfo.setMediaSize(mediaInfo.getSize());
            }
        }

        // 更新数据库中的声音信息
        trackInfoMapper.updateById(trackInfo);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void removeTrackInfo(Long trackId) {
        //1.删除这个专辑下的声音集数
        TrackInfo trackInfo = trackInfoMapper.selectById(trackId);
        if(trackInfo == null){
            throw new GuiguException(201, "声音不存在");
        }
        Long albumId = trackInfo.getAlbumId();
        AlbumInfo albumInfo = albumInfoMapper.selectById(albumId);
        if(albumInfo == null){
            throw new GuiguException(201, "专辑不存在");
        }
        albumInfo.setIncludeTrackCount(albumInfo.getIncludeTrackCount() - 1); // 更新专辑信息
        albumInfoMapper.updateById(albumInfo);


        //2.删除声音的基本信息
        trackInfoMapper.deleteById(trackId);
        //3.删除声音的统计信息
        trackStatMapper.delete(
                new LambdaQueryWrapper<TrackStat>()
                        .eq(TrackStat::getTrackId, trackId));

        // 4.删除vod中的声音
        vodService.removeMedia(trackInfo.getMediaFileId());


    }

    @Override
    public IPage<AlbumTrackListVo> findAlbumTrackPage(Long albumId, IPage<AlbumTrackListVo> albumTrackListVoPage) {
        IPage<AlbumTrackListVo> result = new Page<>();
        // 1.根据专辑id查询该专辑下的所有声音列表(AlbumTrackListVo下的付费图标都是（false）免费)
        IPage<AlbumTrackListVo> albumTrackListFromDb = trackInfoMapper.findAlbumTrackPage(albumTrackListVoPage, albumId);

        // 2.处理声音列表中每一个声音的isShowPaidMark（true/false）
        // 2.1 根据专辑id获取专辑对象
        AlbumInfo albumInfo = albumInfoMapper.selectById(albumId);
        if (albumInfo == null) {
            throw new GuiguException(201, "该专辑不存在");
        }
        // 2.2 获取专辑的付费类型(0101-免费、0102-vip免费、0103-付费)
        String payType = albumInfo.getPayType();
        // 2.3 获取专辑的价格类型(0201-单集 0202-整专辑)
        String priceType = albumInfo.getPriceType();
        // 2.4 获取专辑的免费声音集数
        Integer tracksForFree = albumInfo.getTracksForFree();
        // 2.5 获取用户id(登录用户的id)
        Long userId = AuthContextHolder.getUserId();
        // 2.6 根据专辑的付费类型，判断如何返回声音列表数据
        switch (payType) {
            case "0101":
                // 2.6.1 整个专辑都是免费（所有声音都不用显示付费图标）
                result = albumTrackListFromDb;
                break;
            case "0102":
                // 2.6.2 专辑对vip是免费的。（处理付费图标的展示）
                result = dealPayTypeVip(priceType, tracksForFree, albumTrackListFromDb, userId, albumId);
                break;
            case "0103":
                // 2.6.3 专辑是付费的。（处理付费图标的展示）
                result = dealPayTypeNeedPaid(priceType, tracksForFree, albumTrackListFromDb, userId, albumId);
                break;
        }
        // 3.返回声音列表（处理完付费图标）
        return result;
    }

    @Override
    public TrackStatVo getTrackStatVo(Long trackId) {
        return trackStatMapper.getTrackStatVo(trackId);
    }

    /**
     * 查询用户付费列表
     *
     * @param currentTrackId 当前声音id
     * @return 付费列表
     */
    @Override
    public List<Map<String, Object>> findUserTrackPaidList(Long currentTrackId) {
        //展示当前声音之后的某些集数的声音
        //初始化返回结果
        ArrayList<Map<String, Object>> result = new ArrayList<>();

        // 1.获取当前登录的用户id
        Long userId = AuthContextHolder.getUserId();

        // 2.根据传入的声音ID获取声音对象
        TrackInfo trackInfo = trackInfoMapper.selectById(currentTrackId);
        //  判断声音是否存在
        if (trackInfo == null) {
            throw new GuiguException(201, "购买的声音不存在");
        }
        // 3.获取对应专辑对象的id
        Long albumId = trackInfo.getAlbumId();


        // 4.通过专辑id获取专辑对象
        AlbumInfo albumInfo = albumInfoMapper.selectById(albumId);
        // 判断专辑是否存在
        if (albumInfo == null) {
            throw new GuiguException(201, "购买的声音对应的专辑不存在");
        }

        // 5.获取声音的价格(声音价格)
        BigDecimal price = albumInfo.getPrice();

        // 6.获取当前声音的序列号
        Integer orderNum = trackInfo.getOrderNum();

        // 7.查询当前声音之后的所有声音
        List<TrackInfo> trackInfos = trackInfoMapper.selectList(
                new LambdaQueryWrapper<TrackInfo>()
                .eq(TrackInfo::getAlbumId, albumId)
                .gt(TrackInfo::getOrderNum, orderNum)
                .orderByAsc(TrackInfo::getOrderNum)
                        .last("limit 50"));
        // 8.将当前用户已经购买当前专辑的声音id查询出来
        Map<Long, String> userPaidAlbumTrackListDataMap =
                userInfoFeignClient.getUserPaidAlbumTrackList(userId, albumId)
                        .getData();
        // 判断用户是否购买了当前专辑
        Assert.notNull(userPaidAlbumTrackListDataMap, "远程查询用户购买的当前专辑下的声音调用失败");
        //9.过滤掉用户已经购买的声音id
        List<Long> showIds = trackInfos
                .stream()
                .map(TrackInfo::getId)
                .filter(
                        trackId ->
                                StringUtils
                                        .isEmpty(
                                                userPaidAlbumTrackListDataMap.get(trackId)// 用户已经购买的声音id
                                        ))// 过滤掉当前声音id
                .toList();
        //10.展示购买的声音列表
        int size = showIds.size();
        //3块（本集、后10集、后20集）
        //4块（本集、后10集、后20集 后23集）
        //10.1展示本集
        HashMap<String, Object> nowTrack = new HashMap<>();
        nowTrack.put("name", "本集");
        nowTrack.put("price",price);
        nowTrack.put("trackCount", 0);// 集数不展示（买本集还是其他集最区分）
        result.add(nowTrack);
        // 10.2 获取要展示的块数
        int block = size % 10 == 0 ? size / 10 : size / 10 + 1;
        for (int i = 1; i <= block; i++) {
            int count = i * 10;
            if (count >= size) {
                HashMap<String, Object> block2 = new HashMap<>();
                block2.put("name", "后" + size + "集"); // 名字展示
                block2.put("price", price.multiply(new BigDecimal(size)));// 本集的价格展示
                block2.put("trackCount", size);// 集数不展示（买本集还是其他集最区分）
                result.add(block2);
                break;
            }
            HashMap<String, Object> block1 = new HashMap<>();
            block1.put("name", "后" + count + "集"); // 名字展示
            block1.put("price", price.multiply(new BigDecimal(count)));// 本集的价格展示
            block1.put("trackCount", count);// 集数不展示（买本集还是其他集最区分）
            result.add(block1);
        }

        return result;
    }

    /**
     * 获取付费声音列表
     *
     * @param albumId 专辑id
     * @param userId  用户id
     * @param trackCount  声音数量
     * @param trackId  声音id
     * @return 付费声音列表
     */
    @Override
    public Result getPaidTrackAndFilterCanPayTrack(Long albumId, Long userId, Integer trackCount, Long trackId) {
        // 初始化返回结果
        List<TrackInfo> result = new ArrayList<>();
        // 1.查询当前用户买过的专辑下的声音
        Map<Long, String> userPaidAlbumTrackListDataMap =
                userInfoFeignClient.getUserPaidAlbumTrackList(userId, albumId).getData();
        Assert.notNull(userPaidAlbumTrackListDataMap, "远程查询用户购买的当前专辑下的声音调用失败");

        // 2.查询要买的声音
        // 2.1购买本集
        TrackInfo trackInfo = trackInfoMapper.selectById(trackId);
        Assert.notNull(trackInfo, "购买失败，声音不存在");

        // 3.判断是否买过
        //如果买过
        if (trackCount == 0) {
            //往结果中添加
            result.add(trackInfo);
        }else {
            //否则往结果中添加
            List<TrackInfo> trackInfos = trackInfoMapper.selectList(new LambdaQueryWrapper<TrackInfo>()
                    .eq(TrackInfo::getAlbumId, albumId)// 专辑id
                    .gt(TrackInfo::getOrderNum, trackInfo.getOrderNum())// 大于当前声音的序列号
                    .orderByAsc(TrackInfo::getOrderNum)// 序列号升序排序
                    .last("limit " + trackCount));// 限制查询数量
            result.addAll(trackInfos);
        }

        // 4.过滤掉用户已经购买的声音id
        //将已经买过的返回给order--->前端
        List<Long> longs = new ArrayList<>();
        for (TrackInfo trackInfo1 : result) {
            // 如果用户没有买过该声音，则添加到结果中(即根据TrackId获取已经买过的声音Map的value如果为空)
            if (StringUtils.hasText(userPaidAlbumTrackListDataMap.get(trackInfo1.getId()))) {
                // 将已经买过的声音Id添加到结果中
                longs.add(trackInfo1.getId());
                return Result.build(longs,ResultCodeEnum.ORDER_BUY_ERROR);
            }
        }
        return Result.ok(result);
    }

    /**
     * 处理付费类型的付费
     *
     * @param priceType      价格类型
     * @param tracksForFree  免费声音集数
     * @param albumTrackListFromDb 从数据库中获取的专辑声音列表
     * @param userId         用户id
     * @param albumId        专辑id
     * @return 处理后的专辑声音列表
     */
    private IPage<AlbumTrackListVo> dealPayTypeNeedPaid(String priceType, Integer tracksForFree, IPage<AlbumTrackListVo> albumTrackListFromDb, Long userId, Long albumId) {

        // 用户是否登录
        if (userId == null) {
            List<AlbumTrackListVo> freeAlbumTrackListVo = albumTrackListFromDb.getRecords().stream().filter(albumTrackListVo -> albumTrackListVo.getOrderNum() <= tracksForFree).collect(Collectors.toList());
            return albumTrackListFromDb.setRecords(freeAlbumTrackListVo);
        }

        return getReallyShowTrackListVoIPage(priceType, tracksForFree, albumTrackListFromDb, userId, albumId);
    }

    /**
     * 处理付费类型的vip
     *
     * @param priceType      价格类型
     * @param tracksForFree  免费声音集数
     * @param albumTrackListFromDb 从数据库中获取的专辑声音列表
     * @param userId         用户id
     * @param albumId        专辑id
     * @return 处理后的专辑声音列表
     */
    private IPage<AlbumTrackListVo> dealPayTypeVip(String priceType, Integer tracksForFree, IPage<AlbumTrackListVo> albumTrackListFromDb, Long userId, Long albumId) {

        // 1.判断用户id是否存在
        // 1.1 用户不存在（直接返回免费声音列表）
        if (userId == null) {
            // 用户没登录，直接访问该接口
            // 策略一：一集声音都不让他看，直接抛出异常
            // 策略二：让他看免费的声音。把免费的级数声音返回给他 去看
            List<AlbumTrackListVo> freeAlbumTrackListVo = albumTrackListFromDb.getRecords().stream().filter(albumTrackListVo -> albumTrackListVo.getOrderNum() <= tracksForFree).collect(Collectors.toList());
            return albumTrackListFromDb.setRecords(freeAlbumTrackListVo);
        }
        // 1.2 用户存在
        // 1.2.1 根据用户id获取用户对象
        Result<UserInfoVo> userInfoResult = userInfoFeignClient.getUserInfo(userId);
        UserInfoVo userInfoData = userInfoResult.getData();
        Assert.notNull(userInfoData, "用户不存在");
        // 1.2.2 获取用户身份信息（是否是vip）
        Integer isVip = userInfoData.getIsVip();
        Date vipExpireTime = userInfoData.getVipExpireTime();
        // a)不是vip（把免费集数之外且还没买过的的都显示付费）
        if (isVip == 0 || isVip ==1 && vipExpireTime.before(new Date())) {
            return getReallyShowTrackListVoIPage(priceType, tracksForFree, albumTrackListFromDb, userId, albumId);
        } else {
            // b)是vip 一定没过期
            return albumTrackListFromDb;
        }
    }

    /**
     * 处理付费类型的需要付费
     *
     * @param priceType      价格类型
     * @param tracksForFree  免费声音集数
     * @param albumTrackListFromDb 从数据库中获取的专辑声音列表
     * @param userId         用户id
     * @param albumId        专辑id
     * @return 处理后的专辑声音列表
     */
    private IPage<AlbumTrackListVo> getReallyShowTrackListVoIPage(String priceType, Integer tracksForFree, IPage<AlbumTrackListVo> albumTrackListFromDb, Long userId, Long albumId) {
        // 1.判断单集
        if ("0201".equals(priceType)) {
            // 1.1 获取用户买过的声音列表
            Result<Map<Long, String>> userIsPaidAlbumTrackListResult = userInfoFeignClient.getUserPaidAlbumTrackList(userId, albumId);
            Map<Long, String> userIsPaidAlbumTrackListMap = userIsPaidAlbumTrackListResult.getData();
            if (null == userIsPaidAlbumTrackListMap) {
                throw new GuiguException(201, "远程调用用户微服务获取用户购买的专辑下的声音列表失败");
            }
            // 1.2 过滤以及处理
            List<AlbumTrackListVo> records = albumTrackListFromDb.getRecords().stream().map(
                    albumTrackListVo -> {
                        if (StringUtils.isEmpty(userIsPaidAlbumTrackListMap.get(albumTrackListVo.getTrackId())) && albumTrackListVo.getOrderNum() > tracksForFree) {
                            // 说明没买过
                            albumTrackListVo.setIsShowPaidMark(true);
                        }
                        return albumTrackListVo;
                    }
            ).collect(Collectors.toList());
            return albumTrackListFromDb.setRecords(records);
        }else {   // 2.判断整专辑
            // 2.1 获取用户买过的专辑
            Result<Boolean> userIsPaidAlbumResult = userInfoFeignClient.getUserPaidAlbum(userId, albumId);
            Boolean isPaidAlbum = userIsPaidAlbumResult.getData();
            if (null == isPaidAlbum) {
                throw new GuiguException(201, "远程调用用户微服务获取用户购买的专辑失败");
            }
            // 2.2 过滤以及处理
            // 如果这个专辑买过（直接返回）
            if (isPaidAlbum) {
                return albumTrackListFromDb;
            }
            // 如果这个专辑没有买过(只需判断这个专辑下的声音是否在免费集数中 如果在免费中 直接返回 如果不在 修改付费图标)
            List<AlbumTrackListVo> realShowTrackList = albumTrackListFromDb.getRecords().stream().map(albumTrackListVo -> {
                if (albumTrackListVo.getOrderNum() > tracksForFree) {
                    albumTrackListVo.setIsShowPaidMark(true);
                }
                return albumTrackListVo;
            }).collect(Collectors.toList());
            // 3.返回
            return albumTrackListFromDb.setRecords(realShowTrackList);
        }
    }


    /**
     * 初始化声音统计信息
     * 此方法用于为指定的声音ID创建并初始化统计信息对象，包括播放、收藏、点赞和评论四种统计类型
     * 每种统计信息初始数值为0，确保声音统计信息的完整性和一致性
     *
     * @param trackId 声音ID，用于指定需要初始化统计信息的声音
     */
    @Transactional(rollbackFor = Exception.class)
    public void initTrackStat(Long trackId) {
        // 初始化声音统计类型列表
        ArrayList<String> trackStatTypes = new ArrayList<>();
        trackStatTypes.add(SystemConstant.TRACK_STAT_PLAY);
        trackStatTypes.add(SystemConstant.TRACK_STAT_COLLECT);
        trackStatTypes.add(SystemConstant.TRACK_STAT_PRAISE);
        trackStatTypes.add(SystemConstant.TRACK_STAT_COMMENT);

        // 遍历统计类型列表，为每种类型创建并插入统计信息对象
        for (String trackStatType : trackStatTypes) {
            TrackStat trackStat = new TrackStat();
            trackStat.setTrackId(trackId);
            trackStat.setStatType(trackStatType);
            trackStat.setStatNum(0);

            // 插入统计信息到数据库
            trackStatMapper.insert(trackStat);
        }
    }
}
